/*
 * -----------------------------------------------------------------------------
 * Project    : General LDAP Support (Windows)
 * Contents   : Provide a class that define and implements a general support for
 *            : LDAP.
 * Depends    : UNMANAGED C/C++
 * File       : Win32.Net.LdapClient.h (definitions can be found at
 *            : "Win32.Net.LdapClient.cpp")
 * Copywright : Open Knowledge (c) 2000 - 2007
 * Author     : Eduardo Sobrino
 * Date       : Jun/2000
 * Updated    : Aug/2000  (Added support for message queue [WIN32 only])
 *            : May/2007  (Added code to Kif framework)
 */

#pragma once

#pragma region   // Include Files

// Windows required includes

#include <windows.h>
#include <winldap.h>
//#include <cmqueue.h>

#define _GENERIC

#ifdef _UNICODE
   #define TPCHAR PWCHAR
#else
   #define TPCHAR PCHAR
#endif

// -----------------------------------------------------------------------------

#include <malloc.h>
#include "Win32.String.h"
//#include <clhttpclient.h>
//#include <syslib.h>

#pragma endregion

// -----------------------------------------------------------------------------

namespace Kif
{
namespace Win32
{
namespace Net
{
namespace Ldap
{

// -----------------------------------------------------------------------------
// Define commonlly used naming attributes...

#pragma region   // LDAP Namimg Attributes...

extern TCHAR *LDAP_NAMEATTR_DN ;
extern TCHAR *LDAP_NAMEATTR_ObjectClass ;
extern TCHAR *LDAP_NAMEATTR_CN ;
extern TCHAR *LDAP_NAMEATTR_SAMAccount ;
extern TCHAR *LDAP_NAMEATTR_displayName ;
extern TCHAR *LDAP_NAMEATTR_userPassword ;
extern TCHAR *LDAP_TEMPLATE_ACCT ;
extern TCHAR *LDAP_TEMPLATE_CN ;
extern TCHAR *LDAP_TEMPLATE_USER_DN ;
extern TCHAR *LDAP_TEMPLATE_ACCT_DN ;
extern int   LDAP_NAMEATTR_userPasswordLen ;

#pragma endregion

// -----------------------------------------------------------------------------
// LDIF (LDAP Data Interchange Format) parsing support to handle entries.

typedef struct LdifEntry_
{
   TCHAR *pType ;                    // pointer to type
   TCHAR *pValue ;                   // pointer to value
}  LdifEntry ;

class LdifEntries
{
   private:

   LDAP     *pLdap ;             // pointer to ldap session
   int       iNumAttrs ;         // number of attributes
   LDAPMod **ppAttrs ;           // pointer to a pointer of attributes

   void Free() ;                 // free allocated attributes...

   public:

   ULONG     uLastErr ;          // keep last error
   bool      bWeAreOkey ;        // true if all is still ok...
   ULONG     uLastOp ;           // keep last operation (default LDAP_MOD_ADD)
   Win32::String distingushedName; // pointer to a distingushed name

   void Init(LDAP *pLdap,TCHAR *distingushedName)
   {
      ppAttrs = NULL ;
      this->distingushedName.Copy(distingushedName);
      bWeAreOkey = true ;
      iNumAttrs = 0 ;
      this->pLdap = pLdap;
      uLastOp = LDAP_MOD_ADD;
   }

   LdifEntries(LDAP *pLdap,TCHAR *distingushedName)
   { Init(pLdap,distingushedName); }
   LdifEntries(LDAP *pLdap) { Init(pLdap,NULL); }

  ~LdifEntries()
   {
      if (ppAttrs) Free() ;
   }

   bool SetDistinguisedName(TCHAR *distingushedName)
   {
      this->distingushedName.Copy(distingushedName) ;
      return(this->distingushedName.GetLength() > 0) ;
   }  ;

   bool Merge(LdifEntry *pEntry,ULONG uOperation);
   bool Merge(TCHAR *pType,TCHAR *pVal,ULONG uOperation)
   {
      LdifEntry Entry ;
      Entry.pType  = pType ;
      Entry.pValue = pVal ;
      return(Merge(&Entry,uOperation)) ;
   }

   bool FillEntryFromLine(LdifEntry *pEntry,TCHAR *pLine);

   inline bool GetDistinguisedName(TCHAR *pLine)
   {
      LdifEntry Entry ;
      bool isok = false ;
      if (FillEntryFromLine(&Entry,pLine))
         isok = SetDistinguisedName(Entry.pValue) ;
      bWeAreOkey = isok ;
      return(isok);
   }  // end of GetDistinguisedName

   inline bool Append(TCHAR *pType,TCHAR *pVal)
   {  return(Merge(pType,pVal,uLastOp)); }

   inline bool Append(TCHAR *pLine)
   {
      LdifEntry Entry;
      bool isok = false;
      if (FillEntryFromLine(&Entry,pLine))
         isok = Merge(&Entry,uLastOp);
      bWeAreOkey = isok;
      return(isok);
   }  // end of Append

   inline bool AppendToAdd(TCHAR *pLine)
   {
      uLastOp = LDAP_MOD_ADD ;
      return(Append(pLine)) ;
   }

   inline bool AppendToUpdate(TCHAR *pLine)
   {
      uLastOp = LDAP_MOD_REPLACE ;
      return(Append(pLine));
   }

   inline bool AppendToDelete(TCHAR *pLine)
   {
      uLastOp = LDAP_MOD_DELETE;
      return(Append(pLine));
   }  // end of AppendToDelete

   inline bool AppendToAdd(TCHAR *pType,TCHAR *pVal)
   {
      uLastOp = LDAP_MOD_ADD;
      return(Append(pType,pVal));
   }

   inline bool AppendToUpdate(TCHAR *pType,TCHAR *pVal)
   {
      uLastOp = LDAP_MOD_REPLACE;
      return(Append(pType,pVal));
   }

   inline bool AppendToDelete(TCHAR *pType,TCHAR *pVal)
   {
      uLastOp = LDAP_MOD_DELETE;
      return(Append(pType,pVal));
   }

   inline bool Add()
   {
      if (pLdap && (distingushedName.GetLength() > 0) && ppAttrs)
      {
         uLastErr = ldap_add_s(pLdap,distingushedName.GetBuffer(),ppAttrs);
         return(uLastErr == LDAP_SUCCESS) ;
      }
      else
         return(false) ;
   }  // end of Add

   inline bool Update()
   {
      if (pLdap && (distingushedName.GetLength() > 0) && ppAttrs)
      {
         uLastErr = ldap_modify_s(pLdap,distingushedName.GetBuffer(),ppAttrs);
         return(uLastErr == LDAP_SUCCESS) ;
      }
      else
         return(false) ;
   }  // end of Update

}  ;  // end of LdifEntries

// -----------------------------------------------------------------------------
// LDIF link list for managing arbitrary requests

enum LdifRequest
{
   LdifRequest_Add    = 'A',        // request to add record
   LdifRequest_Delete = 'D',        // request to delete record
   LdifRequest_Modify = 'M',        // request to modify record
   LdifRequest_Uknown = 'U'         // unknown request
}  ;  // end of LdifRequest

typedef struct LdifNode_
{
   Kif::Win32::String Line;         // pointer to a line
   struct LdifNode_ *pNext;         // point to next line
}  LdifNode;

class LdifList
{
   private:
   public:

      LdifRequest eRequest;        // LDIF request
      TCHAR    *pUsersDN;          // DN for users
      TCHAR    *pCN;               // user id request
      TCHAR    *pPassword;         // user password

      LdifNode *pRoot;             // pointer to root
      LdifNode *pLast;             // pointer to last added node

      LdifList() { pRoot = NULL; pLast = NULL; }
     ~LdifList() { Free(); }

      void Free()
      {
         // relase allocated resources
         LdifNode *pNode = pRoot,*pToNext;
         while (pNode)
         {
            pToNext = pNode->pNext;
            free(pNode);
            pNode = pToNext;
         }
         pRoot = pLast = NULL;
      }  // end of Free

      inline bool Add(TCHAR *pInLine)
      {
         bool isok = false ;
         if (pInLine)
         {
            LdifNode *pNode = (LdifNode*)malloc(sizeof(LdifNode));
            if (pNode)
            {
               pNode->Line.Copy(pInLine);
               pNode->pNext = NULL;
               if (pRoot)
                  pLast->pNext = pNode;
               else
                  pRoot = pNode;
               pLast = pNode;
               isok = true;
            }
         }
         return(isok);
      }  // end of Add

}  ;  // end of LdifList

// -----------------------------------------------------------------------------
// declare/define Authentication Methods

enum AUTHENTICATION_METHOD {
   AUTH_ANNONYMOUS   = 1,            // login as nobody
   AUTH_CLEAR_TEXT   = 2,            // user info is visible to through the net
   AUTH_LOGGED_USER  = 3             // this will only work on W2k
}  ;

// declare/define Result Status

enum LDAP_WAIT_RESULT {
   LDAP_WAIT_SUCCESS = 0,            // request succeded...
   LDAP_WAIT_ERROR   = 1,            // an error occurs while waiting...
   LDAP_WAIT_TIMEOUT = 2             // wait timeout occurs...
}  ;

// -----------------------------------------------------------------------------
// declare LdapEntry class that supports searching / traversing entries...

class LdapEntry
{
   private:

   TCHAR       *pAttr;            // pointer to attribute
   TCHAR      **pVals;            // pointer to values array
   int          iVal;             // index of current value
   BerElement  *pBer;             // pointer to ber element
   LDAPMessage *pResult;          // pointer to message result on find...

   public:

   LDAP        *psLdap;           // pointer to ldap object
   LDAPMessage *pEntry;           // message use to traverse directory

   bool  Eof;                     // true if there are no more entries

   Kif::Win32::String NamingContext; 
         
   TCHAR *pEntryName;
   TCHAR *pEntryValue;

   void Init(LDAP *pLdap,TCHAR *DN)
   {
      pEntry = NULL; psLdap = pLdap;
      if (DN != NULL)
         NamingContext.Copy(DN);
      pEntryName = pEntryValue = NULL ; pAttr = NULL; pBer = NULL;
      pVals = NULL; pResult = NULL;
   }

   LdapEntry() { Init(NULL,NULL); }
   LdapEntry(LDAP *pLdap,TCHAR *pDN) { Init(pLdap,pDN); }

  ~LdapEntry() { Free(); }

   void Free()
   {
      if (pEntryValue) free(pEntryValue);
      if (pAttr)       ldap_memfree(pAttr);
      if (pVals)       ldap_value_free(pVals);
      if (pResult)     ldap_msgfree(pResult);

      Init(psLdap,NamingContext.GetBuffer());
   }  // end of Free

   inline bool GetDistinguisedName(Kif::Win32::String &OutDN)
   {
      TCHAR *dn = ldap_get_dn(psLdap,pEntry);

      if (dn)
      {
         OutDN.Copy(dn);
         ldap_memfree(dn);
      }

      return(dn != NULL);
   }  // end of GetDistinguisedName

   bool Find(TCHAR *pBase,ULONG scope,TCHAR *pFilter,TCHAR **pAttrs);
   bool Find(TCHAR *pBase,ULONG scope,TCHAR *pFilter,TCHAR *pAttr);
   bool Find(TCHAR *pBase,ULONG scope,TCHAR *pFilter);
   bool Find(TCHAR *pBase)
   { return(Find(pBase,LDAP_SCOPE_BASE,TEXT("objectclass=*"))); }
   bool Find()
   { return(Find(NULL)); }

   bool First();
   bool Next();

}  ;  // end of LdapEntry

// -----------------------------------------------------------------------------
// declare LdapClient class that encapsulate Ldap API's in an easy to use class

class LdapClient
{
   private:

      DWORD     dwThreadID ;         // thread id
      HANDLE    hThread ;            // handle to thread

      ULONG  uLastErr ;              // last error
      long   DefaultTimeout ;        // default timeout

      // user distinguished name template pointer
      Kif::Win32::String UserDistinguishedNameTemplate;

      int    iNumHttpdHosts ;        // number of HTTPD hosts to send requests to
      TCHAR **pHttpdHosts ;          // HTTPD to send requests to

      TCHAR *pLdapHost ;             // pointe to LDAP host
      TCHAR *pLogUserId ;            // login user id
      TCHAR *pLogPassword ;          // login password

      bool bLogged ;                 // true if logged...
      bool bShutdownQueue ;          // true to shutdown any service

      bool BuildUserDistinguishedName(TCHAR *distingushedName,
         TCHAR *pUserId,int iMaxLen);

      LDAP_WAIT_RESULT WaitForOneResult(int iMessid);

      bool AutoConnect() ;

   public:

      bool   bVerbose;               // true to print / trace execution

      // support for LDAP session...

      LDAP  *psLdap;                // pointer to ldap object

      Kif::Win32::String DefaultNamingContext; // default naming context...

      bool   bLogRequests;          // log ldap requests...
      Kif::Win32::String LogPath;   // path to log file (not null if any)
      TCHAR *pQueueFormatName;      // queue format name
      long   lQueueTimeout;         // queue timeout

      long   lStartUpWait;          // milliseconds to wait before startup
      long   lSleepOnRetry;         // sleep time in milliseconds on retry
      int    iConnectRetry;         // connection retry
      int    iResendRetry;          // resend retry

      LdapClient() ;
     ~LdapClient() ;

      void LogError(TCHAR *pFunc,TCHAR *pMess,TCHAR *pReq,ULONG uErrno);

      inline void SetLogPath(TCHAR *pPath)
      {
         LogPath.Copy(pPath);
      }

      bool SetDefaultNamingContext()
      {
         if (DefaultNamingContext.GetLength() <= 0)
            GetDefaultNamingContext(DefaultNamingContext) ;
         return(DefaultNamingContext.GetLength() > 0) ;
      }

      int  GetDefaultNamingContext(Kif::Win32::String &namingContext);

      bool Connect(TCHAR *pLdapServer);
      bool Connect() { return(Connect(NULL)) ; }

      bool AuthenticateUser(TCHAR *pUserId,TCHAR *pPassword,
         AUTHENTICATION_METHOD eAuthMethod);
      bool AuthenticateUser()
      {  return(AuthenticateUser(NULL,NULL,AUTH_LOGGED_USER)); }

      void Dump(TCHAR *pBase,ULONG scope,TCHAR *pFilter);
      virtual void PrintEntry(TCHAR *pEntity,TCHAR *pAttribute,TCHAR *pValue);

      bool LoadLdif(TCHAR *fname);

      bool ScanLdifList(LdifList *pList);

      bool ParseXmlLdifRequest(TCHAR *pXMLLDIFString,bool *pbBadRequest);
      bool ParseXmlLdifRequest(TCHAR *pXMLLDIFString)
      {
         bool bbadreq ;
         return(ParseXmlLdifRequest(pXMLLDIFString,&bbadreq));
      }

      //long ReadQueue(TCHAR *pFormatName,bool bWaitForShutdownSignal);

      void SetLastError(ULONG pLError) { uLastErr = pLError; }
      bool SetQueue(TCHAR *pFormatName);
      void SetUserDistinguisedNameTemplate(TCHAR *pTemplate);
      void SetTimeout(long lTimeoutInMil)
      {  DefaultTimeout = lTimeoutInMil; }

      inline bool Exists(TCHAR *pEntryName)
      {
         LdapEntry entry;
         entry.psLdap = psLdap;
         entry.NamingContext.Dispose();
         bool wasFound = entry.Find(pEntryName);
         entry.Free();
         return(wasFound);
      }

      inline bool FindEntry(LdapEntry *pEntry,TCHAR *pEntryName)
      {
         pEntry->psLdap = psLdap;
         SetDefaultNamingContext();
         pEntry->NamingContext.Copy(DefaultNamingContext);
         return(pEntry->Find(pEntryName));
      }

      bool AddEntry(TCHAR *pDn,TCHAR *pEntryName,TCHAR *pValue);
      bool DeleteEntry(TCHAR *pEntryName);

      ULONG GetLastError() { return(uLastErr); }

      bool Connected() { return(bLogged != NULL); }
      void Disconnect();

      // threading support for reading a queue...

      bool StartThreadForReadingQueue();
      bool StopReadingQueue();
      bool LoadSettings(TCHAR *pFName);

      // the following will only work on W2K with ADSI/Active Directory...

      bool SetSystemUserPassword(TCHAR *pLoginId,TCHAR *pNewPassword);
}  ;

extern LdapClient *LdapServiceStart(bool bVerbose);

// -----------------------------------------------------------------------------

}  // end of Kif::Win32::Net::Ldap
}  // end of Kif::Win32::Net
}  // end of Kif::Win32
}  // end of Kif


